package controlisolation.lifecyclefsm.stateexecutor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public abstract class StateWork implements Runnable {

    protected static final Logger _logger =
            Logger.getLogger(StateWork.class.getName());
    private Map<String, Object> _workInputs = new HashMap<String, Object>();
    private Map<String, Object> _workOutputs = new HashMap<String, Object>();
    private List<WorkStatus> _workErrors = new ArrayList<WorkStatus>();
    private List<WorkStatus> _workWarnings = new ArrayList<WorkStatus>();
    private List<WorkStatus> _workInfoMessages = new ArrayList<WorkStatus>();
    private StateExecutor _stateExecutor;

    public TimeOutErrorLevel getTimeOutErrorLevel() {
        return _timeOutErrorLevel;
    }

    public void setTimeOutErrorLevel(TimeOutErrorLevel timeOutErrorLevel) {
        this._timeOutErrorLevel = timeOutErrorLevel;
    }

    public long getRuntimeSum() {
        return _runtimeSum;
    }

    private WorkStatus instancetimeoutErrorStatus(StateWork work,InterruptedException ex) {
        WorkStatus status=new WorkStatus(work);
        status.setCode(101);
        status.setAssociatedException(ex);
        status.setDescription("Work took too much time, so it was forced to finish.");
        return status;
    }

    private void generateTimeOutNotification(StateWork  work,InterruptedException ex) {
        WorkStatus status = instancetimeoutErrorStatus(work,ex);
        if (this._timeOutErrorLevel==TimeOutErrorLevel.ERROR)
        {
            this._workErrors.add(status);
        } else if (this._timeOutErrorLevel==TimeOutErrorLevel.WARNING)
        {
            this._workWarnings.add(status);
        } else
        {
            this._workInfoMessages.add(status);
        }
    }
    
    public enum TimeOutErrorLevel { NONE,WARNING,ERROR };
    
    private TimeOutErrorLevel _timeOutErrorLevel=TimeOutErrorLevel.NONE;
    
    /* _maxRunningTime is used by the StateExecutor to monitor if
     * this work is taking too much time to finish.
     */
    private long _maxRunningTime=-1; // -1 means infinite
    // _tickMillis is the time the controller monitoring periodical time.
    private long _tickMillis = 50;
    //time this work has been ran
    private long _timesRan=0;
    private long _lastRuntime=0;
    //Runtime sum
    private long _runtimeSum;
    
    private int _averageSumationHelper=0;
    private int _averageCountHelper=0;
    private int _averageTargetTickHelper=20;
    private double _averageRunTime=-1;
    
    
    public abstract void doWork() throws Exception;

   
    public void addError(WorkStatus e) {
        e.setStatusMessageType(WorkStatus.StatusMessageType.ERROR);
        _workErrors.add(e);
        e.setWork(this);
        _stateExecutor.fireWorkErrorEvent(e);
    }

    public void addWarning(WorkStatus e) {
        e.setStatusMessageType(WorkStatus.StatusMessageType.WARNING);
        _workWarnings.add(e);
        e.setWork(this);
        _stateExecutor.fireWorkWarningEvent(e);
    }
    
    public void addInfoMessage(WorkStatus e) {
        e.setStatusMessageType(WorkStatus.StatusMessageType.INFO);
        _workInfoMessages.add(e);
        e.setWork(this);
        _stateExecutor.fireWorkInfoMessageEvent(e);
    }

    public void addError(int code,String description,Exception ex)
    {
        WorkStatus st=new WorkStatus(this);
        st.setCode(code);
        st.setDescription(description);
        st.setAssociatedException(ex);
        addError(st);
    }
    
    public void addWarning(int code,String description,Exception ex)
    {
        WorkStatus st=new WorkStatus(this);
        st.setCode(code);
        st.setDescription(description);
        st.setAssociatedException(ex);
        addWarning(st);
    }
    
    public void addInfoMessage(int code,String description)
    {
        WorkStatus st=new WorkStatus(this);
        st.setCode(code);
        st.setDescription(description);
        addInfoMessage(st);
    }

    public boolean suscessful() {
        return _workErrors.isEmpty();
    }

    public void setWorkInputs(Map<String, Object> workInputs) {
        this._workInputs = workInputs;
    }

    public Map<String, Object> getWorkOutputs() {
        return _workOutputs;
    }

    public List<WorkStatus> getWorkErrors() {
        return _workErrors;
    }

    public List<WorkStatus> getWorkInfoMessages() {
        return this._workInfoMessages;
    }
    
    public Map<String, Object> getWorkInputs() {
        return _workInputs;
    }


    public boolean hasWarnings() {
        return !(this._workWarnings.isEmpty());
    }

    public boolean hasInfoMessages() {
        return !(this._workInfoMessages.isEmpty());
    }
    
    public List<WorkStatus> getWorkWarnings() {
        return _workWarnings;
    }

    public StateExecutor getStateExecutor() {
        return _stateExecutor;
    }

    public void setStateExecutor(StateExecutor stateExecutor) {
        this._stateExecutor = stateExecutor;
    }

    public Map<String, Object> getGlobals() {
        return this._stateExecutor.getContainerFSM().getGlobals();
    }

    public String toString() {
        StringBuffer strb = new StringBuffer();
        strb.append("WORK:");
        strb.append(this.getClass().getName());
        strb.append("\n");

        return strb.toString();
    }

    public static String toStringWorkInputs(StateWork w) {
        StringBuffer strb = new StringBuffer();
        strb.append("\tWORK INPUTS:\n");
        for (String key : w.getWorkInputs().keySet()) {
            strb.append("\t\t[" + key + "|");
            Object obj = w.getWorkInputs().get(key);
            strb.append(obj.toString() + "]\n");
        }
        return strb.toString();
    }

    public static String toStringWorkOutputs(StateWork w) {
        StringBuffer strb = new StringBuffer();
        strb.append("\tWORK OUTPUTS:\n");
        for (String key : w.getWorkOutputs().keySet()) {
            strb.append("\t\t[" + key + "|");
            Object obj = w.getWorkOutputs().get(key);
            strb.append(obj.toString() + "]\n");
        }
        return strb.toString();
    }

    public void pauseFor(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException ex) {
            generateTimeOutNotification(this,ex);
        }
    }

    @Override
    public void run() {
        try {
            //setRunning(true);
            doWork();
            //setRunning(false);
            Thread.yield();
            if (Thread.currentThread().isInterrupted()) {
                throw new InterruptedException("Stopped by ifInterruptedStop()");
            }
           
        } catch (InterruptedException ex) {
           // _logger.log(Level.INFO, null, ex);
            generateTimeOutNotification(this,ex);
            
        } catch (Exception ex) {
            _logger.log(Level.SEVERE, null, ex);
        }


    }

    /*
    public boolean isRunning() {
        return _running;
    }

    public void setRunning(boolean running) {
        this._running = running;
    }*/

    public long getMaxRunningTime() {
        return _maxRunningTime;
    }

    public void setMaxRunningTime(long maxRunningTime) {
        this._maxRunningTime = maxRunningTime;
    }

    public long getTickMillis() {
        return _tickMillis;
    }

    public void setTickMillis(long tickMillis) {
        this._tickMillis = tickMillis;
    }

    public long getTimesRan() {
        return _timesRan;
    }

    public void increaseTimesRan() {
        _timesRan++;
        
    }
    
    public double getAverageRuntime()
    {
        return _averageRunTime;
    }


    public void registerRunTime(long runtime) {
        _lastRuntime+=runtime;
        _runtimeSum += runtime;
        _averageSumationHelper+=runtime;
        _averageCountHelper++;
        
        // Check if it is time to update the average
        if (_averageCountHelper==_averageTargetTickHelper)
        {
           _averageRunTime=(double)_averageSumationHelper/(double)_averageTargetTickHelper;
           _averageCountHelper=0; 
        }
    }

    public long getLastRuntime() {
        return _lastRuntime;
    }
}
